【chef】chef-serverでrun_listを利用したserverspecでのテスト
はじめに
こんにちは植木和樹です。先日chef-soloでのテスト自動化をブログにまとめました。多くの方に関心を持っていただけたようで、苦労して書いた身として大変うれしく思います。
【AWS】JenkinsとserverspecでChefのテストを自動化する
さて本日はその応用、chef-serverを利用した際のテストについて書いてみたいと思います。応用と書きましたが実際やってみたところテストの方法としては全くの別物になりました。なぜchef-soloとchef-serverではテストの方法が異なるのか、についても考えてみたいと思います。
chef-soloとchef-serverについて
「サーバ環境を構築するツール」という観点でchef-soloとchef-serverを比べ、それぞれが使われる規模やサーバの運用状況、テストを実施する際の状況について考えました。
■chef-soloで想定される運用
- サーバ台数は少ない
- 一度構築したらあまり更新しない
- run_listはrecipe単位
- テストはcookbookを作成後、確認のために実施する
■chef-serverで想定される運用
- サーバ台数が多い
- 初期構築後も定期的にchef-clientを実行して環境を同期(収束)し続ける
- run_listはrole単位
- テストはcookbookの適用に関わらず、日常的に実施する
chef-soloとchef-serverでは大きく運用の仕方が異なっています。まったくの別物と言っても良いでしょう。さらにchef-soloとchef-serverそれぞれの環境でテストのシナリオを想定し表にまとめてみました。前回ブログのchef-soloは左列でのテストを想定しています。
chefの種類 | chef-solo | chef-server |
---|---|---|
テスト対象のサーバは | テスト用に新たに起動する | すでに起動済み |
つまりテスト対象インスタンスが | テスト毎に異なる | 常に同じサーバ |
テスト対象サーバは | 1台のみ | 複数台 |
これらのサーバに対して テストと同時にChef Cookbooksを |
適用する | 適用しない |
適用するCookbooksは | 1パターンのみ | テストインスタンス毎に異なる |
run_listの指定は | ノード単位 | role単位 |
chefを実行する際のトリガーは | 外部からキックされる | ノード自ら実行する |
テスト完了後にテスト用インスタンスを | 終了する | 終了しない |
もちろんchef-soloで右列のシナリオができないということではありません。chef-serverで運用する規模で、典型的と思われるテストシナリオとして考えてみました。今回は右のテストシナリオを実現する方法を考えてみたいと思います。
テスト方法のポイント
先にあげた表の中で、今回特に検討しなければならないのは以下の3点です。
- テスト対象ノードの台数が「複数台」
- テストするCookbooksが「テストインスタンスによって異なる」
- run_listの指定はrole単位
つまりテスト実行時に次の処理が必要になります。
- テスト対象ノードの一覧を取得し
- ノードに適用されたrun_listを取得し
- run_listにroleが含まれていればrecipe(cookbook)に展開する
適用されたcookbookに応じたserverspecを実行するRakefile
以上の処理を実現するRakefileは以下になります。chef-soloと大きく異るポイントをハイライトしています。ハイライトされた行それぞれでchef-serverに問い合わせて、情報を取得しています。
前回に続き、多大なるヒント(というかほぼ解決策)をいただいた @kenjiskywalker さんに、この場を借りてお礼を申し上げたいと思います。本当にありがとうございます。【参考サイト】「Chefの中身読んで、外部からrun_listを利用する」
なお一点お断りなのですが、動作確認はchef-zeroで行っているため、chef-serverでは異なる部分があるかもしれません。
ファイル:Rakefile
require 'rubygems' require 'chef' require 'rspec/core/rake_task' require 'json' require 'chef/run_list' desc "Run serverspec to all hosts" task :default => 'serverspec:all' host_run_list = {} namespace :serverspec do Chef::Config[:client_key] = '/etc/chef/client.pem' Chef::Config[:chef_server_url] = 'http://chef-server.classmethod.jp/' Chef::Config[:node_name] = `hostname` host_run_list = Chef::Node.list ### start task task :all => host_run_list.keys host_run_list.keys.each do |target_host| ### fetch test node info from chef-server node = Chef::Node.find_or_create(target_host) desc "Run serverspec to #{target_host}" RSpec::Core::RakeTask.new(target_host.to_sym) do |t| ENV['TARGET_HOST'] = target_host ### fetch run_list of target_host target_run_list = node.run_list ### expand roles to recipes recipes = target_run_list.expand("_default", "server").recipes cookbooks = recipes.map { |r| r.sub(/::.+$/, "") }.uniq t.pattern = [ 'site-cookbooks/{' + cookbooks.join(',') + '}/spec/serverspec/*_spec.rb', "spec/#{target_host}/*_spec.rb" ] end end end
specディレクトリに作成するspec_helper.rbは前回のchef-soloと同じ内容です。
ファイル:spec/spec_helper.rb
require 'serverspec' require 'pathname' require 'net/ssh' require 'highline/import' include Serverspec::Helper::Ssh include Serverspec::Helper::DetectOS RSpec.configure do |c| c.host = ENV['TARGET_HOST'] options = Net::SSH::Config.for(c.host) user = options[:user] || Etc.getlogin c.ssh = Net::SSH.start(c.host, user, options) c.os = backend.check_os end
Chef::Node.listでノード一覧を取得しています。これはホスト名をキーとしたハッシュで返るので、キーのみを取り出してserverspec内で(ssh)接続しています。前回同様、serverspecを実行するノードから、テスト対象のノードへssh接続ができるよう ~/.ssh/config ファイルを修正しておいてください。
Jenkinsのジョブに登録する場合
テスト環境の前提に書いたとおり、今回のテスト環境ではテスト対象のサーバがすでに稼働していることを想定しています。そのためテスト用EC2インスタンスの起動と終了は不要になるので、ジョブ「01_serverspec-run-instance」と「03_serverspec-destroy-instance」は不要です。
必要なジョブは「02_serverspec-git-clone」のみになりますが、このジョブで実行しているシェルも、テスト用EC2インスタンスの動的IPアドレスを調べる必要がなくなるので、以下のようにとてもシンプルになります。
cd ${WORKSPACE} rake
最後に
今回はchef-serverを用いた環境でのテストについて考えてみました。chef-serverを使うと、テスト対象ノードの情報すべてをサーバから取得できるので、chef-soloの時のようにaws-cliを駆使する必要がなく非常にシンプルになりました。
テストをする環境やシナリオは現場ごとにそれぞれだと思いますので、これが正しい答えというものはありません。みなさんの環境に応じて見なおしていただければと思います。